package sbc.group1.lab1.mozartspaces;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.mozartspaces.capi3.AnyCoordinator;
import org.mozartspaces.capi3.CountNotMetException;
import org.mozartspaces.capi3.InvalidContainerException;
import org.mozartspaces.capi3.Selector;
import org.mozartspaces.core.ContainerReference;
import org.mozartspaces.core.Entry;
import org.mozartspaces.core.MzsConstants;
import org.mozartspaces.core.MzsCoreException;
import org.mozartspaces.core.MzsCoreRuntimeException;
import org.mozartspaces.core.RequestFuture;
import org.mozartspaces.core.TransactionReference;
import org.mozartspaces.core.MzsConstants.RequestTimeout;
import org.mozartspaces.core.util.Nothing;
import org.mozartspaces.notifications.Notification;
import org.mozartspaces.notifications.NotificationListener;
import org.mozartspaces.notifications.NotificationManager;
import org.mozartspaces.notifications.Operation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sbc.group1.lab1.common.Assembler;
import sbc.group1.lab1.common.Car;
import sbc.group1.lab1.common.CarBody;
import sbc.group1.lab1.common.Engine;
import sbc.group1.lab1.common.Order;
import sbc.group1.lab1.common.Wheel;
import sun.misc.Signal;
import sun.misc.SignalHandler;

class AssemblerImpl implements Runnable, Assembler, NotificationListener {

    public static final Logger log =
            LoggerFactory.getLogger(AssemblerImpl.class);

    private static final Selector wheelsSelector =
            AnyCoordinator.newSelector(Car.NUM_WHEELS);
    private static final Selector engineSelector =
            AnyCoordinator.newSelector(1);
    private static final Selector bodySelector =
            AnyCoordinator.newSelector(1);

    public static void main(String[] args) throws MzsCoreException,
            URISyntaxException, IOException, InterruptedException {
        AssemblerImpl assembler =
                new AssemblerImpl(new File(args[0]).toURI().toURL(),
                        new URI(args[1]), Integer.parseInt(args[2]));
        final Thread t = new Thread(assembler, "assembler");
        Signal.handle(new Signal("INT"), new SignalHandler() {
            @Override
            public void handle(Signal sig) {
                t.interrupt();
            }
        });

        if (System.getProperty("benchmark") != null && System.getProperty("benchmark").equals("true")) {
            Signal.handle(new Signal("HUP"), new SignalHandler() {
                @Override
                public void handle(Signal sig) {
                    t.start();
                }
            });
        } else {
            t.start();
            System.in.read();
            t.interrupt();
            t.join();
        }
    }

    private final SpaceClient space;
    private volatile boolean running;
    private final int workerId;
    private final ExecutorService executor =
            Executors.newFixedThreadPool(10);

    private volatile Order currentOrder = null;
    private volatile boolean resetOrder=true;
    private NotificationManager notificationManager;
    private Notification orderNotification;
    
    public AssemblerImpl(URL configFile, URI spaceURI, int workerId)
            throws MzsCoreException, InterruptedException {
        space = new SpaceClient();
        space.init(configFile, spaceURI);
        this.workerId = workerId;
        this.notificationManager = new NotificationManager(space.getCore());
        
    }
@Override
    public void run() {
        log.info("Starting");
        running = true;
        try {
            createNotifications();
            while (running) {
                try {
                    doWork();
                    log.debug("Car successfully assembled.");
                } catch (MzsCoreException e1) {
                    resetOrder=true;
                    log.error("Car assembly failed!", e1);
                } catch (InterruptedException e) {
                    log.info("Interrupted! Stopping...");
                    running = false;
                    return;
                } catch (MzsCoreRuntimeException e) {
                    if (e.getCause() instanceof InterruptedException) {
                        log.info("Interrupted! Stopping...");
                    } else {
                        log.error("", e);
                    }
                    running = false;
                    return;
                }
            }
        } catch (MzsCoreException e) {
            log.error("",e);
            return;
        } catch (InterruptedException e) {
            log.error("",e);
            return;
        } finally {
            executor.shutdownNow();
            notificationManager.shutdown();
            space.getCore().shutdown(true);
        }
    }

    private void createNotifications() throws MzsCoreException, InterruptedException {
        HashSet<Operation> writeOp = new HashSet<Operation>();
        writeOp.add(Operation.WRITE);
            notificationManager.createNotification(
                    space.getCarBodyContainer(), 
                    this, writeOp, (TransactionReference)null, null);
            notificationManager.createNotification(
                    space.getEngineContainer(), 
                    this, writeOp, (TransactionReference)null, null);
            orderNotification=notificationManager.createNotification(
                    space.getOrderContainer(), 
                    this, writeOp, (TransactionReference)null, null);
            notificationManager.createNotification(
                    space.getOrderJobsReissueContainer(), 
                    this, writeOp, (TransactionReference)null, null);
    }
    private void doWork() throws MzsCoreException, InterruptedException {
        if (resetOrder) {
            resetOrder=false;
            currentOrder = getNextOrder(null);
        }
        if (currentOrder == null) {
            doWorkWithoutOrder();
        } else {
            if (doWork(currentOrder)) {
            } else {
                currentOrder = getNextOrder(currentOrder);
            }
        }
    }

    private void doWorkWithoutOrder() throws MzsCoreException,
            InterruptedException {
        final TransactionReference transaction =
                space.getCapi().createTransaction(RequestTimeout.INFINITE,
                        space.getSpaceUri());
        boolean doRollback = true;
        try {
            Engine engine =
                    space.getCapi().<Engine>take(
                            space.getEngineContainer(), engineSelector,
                            RequestTimeout.INFINITE, transaction).get(0);
            CarBody body =
                space.getCapi().<CarBody>take(
                        space.getCarBodyContainer(), bodySelector,
                        RequestTimeout.INFINITE, transaction).get(0);
            if (!resetOrder) {
                HashSet<Wheel> wheels =
                        new HashSet<Wheel>(space.getCapi().<Wheel>take(
                                space.getWheelContainer(), wheelsSelector,
                                RequestTimeout.INFINITE, transaction));
    
                Car newCar = new Car(body, engine, wheels, this);
                ContainerReference writeContainer;
                if (newCar.isCoated()) {
                    writeContainer = space.getCompletedCarContainer();
                } else {
                    writeContainer = space.getAssembledCarContainer();
                }
                RequestFuture<Nothing> writeOp =
                        space.getAcapi().write(writeContainer,
                                RequestTimeout.INFINITE, transaction,
                                new Entry(newCar));
                executor.execute(new FailedTransactionHandler(writeOp, space
                        .getAcapi().commitTransaction(transaction), transaction,
                        space.getCapi()));
                doRollback = false;
            }
        } finally {
            if (doRollback) {
                space.getCapi().rollbackTransaction(transaction);
            }
        }
    }

    private boolean deleteAssemblyJob(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try{
            space.getCapi().delete(space.getAssemblyJobContainer(order),
                    (Selector)null,
                    MzsConstants.RequestTimeout.TRY_ONCE,
                    transaction);
        }catch(CountNotMetException e) {
            return false;
        } catch (InvalidContainerException e) {
            return false;
        }
        return true;
    }
    
    private CoatingJob takeCoatingJob(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try {
            return space.getCapi().<CoatingJob>take(space.getCoatingJobContainer(order),
                    (Selector)null,
                    MzsConstants.RequestTimeout.TRY_ONCE,
                    transaction).get(0);
        }catch(CountNotMetException e) {
            return null;
        }
    }
    
    private Engine takeEngine(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try {
            return space.getCapi().<Engine> take(
                            space.getEngineContainer(),
                            getEngineSelector(order.getEngine()),
                            RequestTimeout.TRY_ONCE, transaction).get(0);
        } catch (CountNotMetException e) {
            return null;
        }
    }
    
    private CarBody takeCarBodyByColor(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try {
            return
                    space.getCapi().<CarBody> take(
                            space.getCarBodyContainer(),
                            getCoatedBodySelector(order.getColor()),
                            RequestTimeout.TRY_ONCE, transaction).get(0);
        } catch (CountNotMetException e) {
            return null;
        }
    }
    
    private CarBody takeBlankCarBody(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try {
            return
                    space.getCapi().<CarBody> take(
                            space.getCarBodyContainer(),
                            getBlankBodySelector(),
                            RequestTimeout.TRY_ONCE, transaction)
                            .get(0);
        } catch (CountNotMetException e2) {
            return null;
        }
    }
    
    private CarBody takeCarBodyCoatedForOrder(
            final TransactionReference transaction, 
            final Order order) throws MzsCoreException {
        try {
            return
                    space.getCapi().<CarBody> take(
                            space.getCoatedCarBodyContainer(order),
                            (Selector)null,
                            RequestTimeout.TRY_ONCE, transaction)
                            .get(0);
        } catch (CountNotMetException e2) {
            return null;
        }
    }
    
    private boolean doWork(final Order order) throws MzsCoreException {
        final TransactionReference transaction =
                space.getCapi().createTransaction(RequestTimeout.INFINITE,
                        space.getSpaceUri());
        boolean doRollback = true;
        try {
            if (!deleteAssemblyJob(transaction, order)) {
                return false;
            }
            Engine engine = takeEngine(transaction, order);
            if (engine == null) {
                return false;
            }
            CoatingJob coatingJob = takeCoatingJob(transaction, order);
            CarBody body=null;
            if (coatingJob==null) {
                body = takeCarBodyCoatedForOrder(transaction, order);
                if (body==null) {
                    return false;
                }
            }else {
                body=takeCarBodyByColor(transaction, order);
                if (body==null) {
                    body = takeBlankCarBody(transaction, order);
                    if (body==null) {
                        body = takeCarBodyCoatedForOrder(transaction, order);
                        if (body==null) {
                            return false;
                        }else {
                            space.getCapi().write(space.getCoatingJobContainer(order),
                                    MzsConstants.RequestTimeout.INFINITE,
                                    transaction, new Entry(coatingJob));
                        }
                    }
                }
            }
            HashSet<Wheel> wheels =
                    new HashSet<Wheel>(space.getCapi().<Wheel> take(
                            space.getWheelContainer(), wheelsSelector,
                            RequestTimeout.INFINITE, transaction));
            Car newCar = new Car(body, engine, wheels, this, order);
            ContainerReference writeContainer;
            if (newCar.isCoated()) {
                writeContainer = space.getCompletedCarContainer();
            } else {
                writeContainer = space.getAssembledCarContainer();
            }
            RequestFuture<Nothing> writeOp =
                    space.getAcapi().write(writeContainer,
                            RequestTimeout.INFINITE, transaction,
                            new Entry(newCar));
            executor.execute(new FailedTransactionHandler(writeOp, space
                    .getAcapi().commitTransaction(transaction), transaction,
                    space.getCapi()));
            doRollback = false;
            return true;
        } finally {
            if (doRollback) {
                space.getCapi().rollbackTransaction(transaction);
            }
        }
    }

    private Order getNextOrder(Order currentOrder) throws MzsCoreException {
        try {
            if (currentOrder==null) {
                return space.getCapi().<Order>read(space.getOrderContainer(),
                        OrderCoordinator.newSelector(1),
                        MzsConstants.RequestTimeout.TRY_ONCE, (TransactionReference)null).get(0);
            }else {
                return space.getCapi().<Order>read(space.getOrderContainer(),
                    OrderCoordinator.newSelector(1, currentOrder.getId()+1),
                    MzsConstants.RequestTimeout.TRY_ONCE, (TransactionReference)null).get(0);
            }
        }catch(CountNotMetException e) {
            return null;
        }
    }

    private Selector getCoatedBodySelector(Color color) {
        return ColorCoordinator.newSelector(color);
    }

    private Selector getBlankBodySelector() {
        return LabelCoatablesContainerAspect.CoatableLabel.NOT_COATED.getSelector();
    }

    private Selector getEngineSelector(Engine.Type engineType) {
        return LabelEnginesContainerAspect.getSelector(engineType);
    }

    @Override
    public int getId() {
        return workerId;
    }

    @Override
    public void entryOperationFinished(Notification source,
            Operation operation, List<? extends Serializable> entries) {
        if (source==orderNotification) {
            for (Serializable serial : entries) {
                if (serial instanceof Entry) {
                    Object value;
                    //is always an Entry here, but depends on operation
                    if (serial instanceof Entry) {
                        value = ((Entry)serial).getValue();
                    }else {
                        value = serial;
                    }
                    try {
                        notificationManager.createNotification(
                                space.getCoatedCarBodyContainer((Order)value), 
                                this, Operation.WRITE);
                        resetOrder=true;
                    } catch (MzsCoreException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            
            if (currentOrder==null) {
                resetOrder=true;
            }
        }else {
            resetOrder=true;
        }
    }
}
